/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.jfonia.connect5.intervals;

import java.util.ArrayList;
import java.util.HashMap;
import org.jfonia.connect5.basics.AbstractValueNode;
import org.jfonia.connect5.basics.BasicValueNode;
import org.jfonia.connect5.basics.MutableValueNode;
import org.jfonia.connect5.basics.ValueNode;
import org.jfonia.connect5.relations.Equal;

/**
 *
 * @author Rik Bauwens
 */
public class LinkedDoubleIntervalList implements Interval
{
	MutableValueNode<Double> begin = new BasicValueNode(0.0);
	MutableValueNode<Double> end   = new BasicValueNode(0.0);
	ValueNode<Double> difference   = new AbstractValueNode<Double>()
	{
		public Double getValue()
		{
			return end.getValue() - begin.getValue();
		}

		public Class<?> getType()
		{
			return Double.class;
		}
	};

	ArrayList<Interval<Double>> elements = new ArrayList<Interval<Double>>(); // n
	HashMap<MutableValueNode<Double>, Equal<Double>> relations = new HashMap<MutableValueNode<Double>, Equal<Double>>(); // relation[i] is equality i-1,i

	public LinkedDoubleIntervalList()
	{
		makeLink(0);
	}

	public MutableValueNode<Double> getBeginNode()
	{
		return begin;
	}

	public MutableValueNode<Double> getEndNode()
	{
		return end;
	}

	public ValueNode<Double> getDifferenceNode()
	{
		return difference;
	}

	public Double getBegin()
	{
		return begin.getValue();
	}

	public Double getEnd()
	{
		return end.getValue();
	}

	public Double getDifference()
	{
		return difference.getValue();
	}

	public int size()
	{
		return elements.size();
	}

	public Interval<Double> add(Interval<Double> ts)
	{
		return insert(size(), ts);
	}

	public Interval<Double> insert(int pos, Interval<Double> val)
	{
		if (pos < 0 || pos > elements.size())
			throw new IndexOutOfBoundsException();
		removeLink(pos);
		elements.add(pos, val);
		makeLink(pos);
		makeLink(pos + 1);
		//assert relations.size() == elements.size() + 1;
		return val;
	}

	public Interval<Double> remove(int i)
	{
		assert (i >= 0 && i < elements.size());

		removeLink(i);
		removeLink(i + 1);
		Interval<Double> val = elements.remove(i);
		makeLink(i);
		//assert relations.size() == elements.size() + 1;
		return val;
	}


	protected MutableValueNode<Double> getRight(int pos)
	{
		return pos < this.size() ? elements.get(pos).getBeginNode() : this.end;
	}

	protected MutableValueNode<Double> getLeft(int pos)
	{
		return pos > 0 ? elements.get(pos - 1).getEndNode() : this.begin;
	}

	protected void makeLink(int pos)
	{
		assert(pos >= 0 && pos <= size());
		MutableValueNode<Double> left = getLeft(pos);
		MutableValueNode<Double> right = getRight(pos);
		relations.put(left, new Equal<Double>(left, right)); // right is set equal to left
	}

	protected void removeLink(int pos)
	{
		assert(pos >= 0 && pos <= size());
		MutableValueNode<Double> left = getLeft(pos);
		MutableValueNode<Double> right = getRight(pos);
		Equal eq = relations.remove(left);
		eq.switchOff();
	}

        public Interval getIntervalFromIndex(int index){
            return elements.get(index);
        }

        public Interval<Double> getIntervalFromValue(double value){
            if(value >= begin.getValue() && value <= end.getValue()){
                int pos = 0;
                while(pos < elements.size() && elements.get(pos).getBegin() <= value){
                    pos++;
                }
                pos--;
                return elements.get(pos);
            }else{
                throw new IndexOutOfBoundsException("index "+value+", not in ["+begin.getValue()+","+end.getValue()+"]");
            }
        }

        public int indexOf(Interval<Double> interval){
            return elements.indexOf(interval);
        }

        public void stretch(double newDifference){
            double currentLength = difference.getValue();
            double factor = newDifference/currentLength;
            for(int i = 0; i < elements.size(); i++){
                Interval<Double> val = elements.get(i);
                double diff = (factor * val.getDifference());
                val.getEndNode().setValue(val.getBegin()+diff);
            }
            getEndNode().setValue(newDifference);
        }

        public void clear()
        {
                //Disable relations
                for(Equal<Double> relation : relations.values())
                        relation.switchOff();
                //Remove relations
                relations.clear();
                //Remove intervals
                elements.clear();
                //Make link between begin and end node
                makeLink(0);
        }
}
